#pragma once

#include <algorithm>
#include <sstream>
#include <unistd.h>
#include <jsoncpp/json/json.h>

#include "Index.hpp"

namespace SearchArea
{
    using namespace LogArea;

#define PREV_SIZE 50
#define NEXT_SIZE 100

    typedef struct InvertedElemPrint
    {
        uint64_t doc_id;
        int weight;
        std::vector<std::string> words;
        InvertedElemPrint() : doc_id(0), weight(0) {}
    }InvertedElemPrint;

    class Searcher
    {
    public:
        Searcher() : _index(nullptr)
        {
        }

        void InitSearch(const std::string &input)
        {
            // 1. 获取或创建 index 对象
            _index = IndexArea::Index::GetInstance();
            LogArea::LOG(LogLevel::DEBUG) << "GetInstance Success...";
            // 2. 根据 index 对象建立索引
            _index->BuildIndex(input);
            LogArea::LOG(LogLevel::DEBUG) << "BuildIndex Success...";
        }

        // query: 用户的查询
        // json_string: 返回给用户浏览器的搜索结果
        void Search(const std::string &query, std::string &json_string)
        {
            // 1.分词: 对用户的查询进行分词
            std::vector<std::string> words;
            UtilArea::JiebaUtil::CutString(query, words);

            // 2.触发: 根据分好的每个词进行索引查找,建立index时是忽略大小写的,因此查找时也要忽略
            // IndexArea::InvertedIndexList invered_list_all; // vector<InvertedIndex>,存储的是InvertedIndex节点

            // 将搜索出来的结果去重
            std::vector<InvertedElemPrint> invered_list_all;
            std::unordered_map<uint64_t, InvertedElemPrint> tokens_map;
            for (auto &word : words)
            {
                boost::to_lower(word);
                LogArea::LOG(LogLevel::DEBUG) << "word: " << word;
                // 根据关键词获取倒排拉链
                IndexArea::InvertedIndexList *invered_list = _index->GetInverIndex(word);
                if (nullptr == invered_list)
                    continue;
                
                for(auto& elem : *invered_list)
                {
                    auto& item = tokens_map[elem.doc_id];   // 存在则获取,不存在则插入:去重操作
                    item.doc_id = elem.doc_id;
                    item.weight+=elem.weight;   // 将重复文档的权重累加
                    item.words.emplace_back(elem.word);
                }

                // 存储去重后的搜索结果
                for(const auto& item : tokens_map)
                {
                    invered_list_all.emplace_back(item.second);
                }
                // 将 invered_list 中的节点一个个插入 invered_list_all 中
                // invered_list_all.insert(invered_list_all.end(), invered_list->begin(), invered_list->end());
            }

            // 3.合并排序: 根据索引查找到的结果,按照相关性(weight)进行降序排序
            std::sort(invered_list_all.begin(), invered_list_all.end(),
                      [](const InvertedElemPrint &e1, const InvertedElemPrint &e2)
                      {
                          return e1.weight > e2.weight;
                      });

            // 4.构建: 根据查找出来的结果,构建json串
            Json::Value root;
            std::stringstream ss;
            for (auto &item : invered_list_all)
            {
                // 用 doc_id 通过正排查找到DocInfo
                IndexArea::DocInfo *doc = _index->GetForwardIndex(item.doc_id);
                if (nullptr == doc)
                    continue;
                Json::Value elem;
                elem["title"] = doc->title;
                // elem["abstract"] = doc->content;    // 这里的 content 是文档的整个内容,我们要的是摘要,后面要改 TODO
                elem["abstract"] = GetAbstract(doc->content, item.words[0]);
                elem["url"] = doc->url;

                elem["id"] = (int)(item.doc_id);
                elem["weight"] = item.weight;

                root.append(elem);
            }

            // 将 Json 串回显给用户
            Json::StreamWriterBuilder swb;
            std::shared_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
            int ret = sw->write(root, &ss);
            if (ret != 0)
                LogArea::LOG(LogLevel::ERROR) << "Json write error";

            Json::FastWriter writer;
            json_string = writer.write(root);
        }

        std::string GetAbstract(const std::string &content, const std::string &word)
        {
            // 定位到 word 在文档中首次出现的位置,随后向前截取50字节(如果不够,从begin开始) + 向后截取100字节(如果不够,截取到末尾)
            // 1. 定位首次出现的位置

            // 这种写法有问题:
            // 因为 content 是原始文档里的内容,区分大小写;而word是to_lower后的字符串,不区分大小写
            // size_t pos = content.find(word);
            // if(pos == std::string::npos)    // 这种情况不可能出现
            // {
            //     LogArea::LOG(LogLevel::WARNING)<<"Not found word";
            //     return "None word";
            // }

            // 因此这里使用 algorithm 中的 search 进行查找,可以忽略大小写式地查找
            auto iter = std::search(content.begin(), content.end(), word.begin(), word.end(), [](char x, char y)
                                    { return (std::tolower(x) == std::tolower(y)); });

            if (iter == content.end())
            {
                LogArea::LOG(LogLevel::WARNING) << "None content";
                return "None content";
            }

            int pos = std::distance(content.begin(), iter);

            // 确定要截取的区间
            int begin = 0;
            int end = content.size() - 1;
            if (pos >= PREV_SIZE)
                begin = pos - PREV_SIZE;
            if (pos <= end + NEXT_SIZE)
                end = pos + NEXT_SIZE;

            if (begin >= end)
            {
                LogArea::LOG(LogLevel::WARNING) << "None content";
                return "None content";
            }

            return content.substr(begin, end - begin);
        }

        ~Searcher()
        {
        }

    private:
        IndexArea::Index *_index; // 供系统进行查找的索引
    };

}